#ifndef XAGLOG_HPP_
#define XAGLOG_HPP_

/* ########## Some macros that can be set ########## */
// #define XAGLOG_CONSOLE        // Console output log
// #define XAGLOG_MORE_CAPACITY  // Log capacity increase
#define XAGLOG_USE_TRACE_LOG  // Use traceability log
// #define XAGLOG_USE_PRINTF     // Enter the log using printf
// #define XAGLOG_USE_STREAM     // Enter the log using stream

/* ########## This file is all SPDLOG replaced ########## */
// constexpr char xaglogger[] = "FMS";
// #undef SPDLOG_TRACE
// #undef SPDLOG_DEBUG
// #undef SPDLOG_INFO
// #undef SPDLOG_WARN
// #undef SPDLOG_ERROR
// #undef SPDLOG_CRITICAL
// #define SPDLOG_TRACE(...) XAGLOG_TOPIC_TRACE(xaglogger, ##__VA_ARGS__)
// #define SPDLOG_DEBUG(...) XAGLOG_TOPIC_DEBUG(xaglogger, ##__VA_ARGS__)
// #define SPDLOG_INFO(...) XAGLOG_TOPIC_INFO(xaglogger, ##__VA_ARGS__)
// #define SPDLOG_WARN(...) XAGLOG_TOPIC_WARN(xaglogger, ##__VA_ARGS__)
// #define SPDLOG_ERROR(...) XAGLOG_TOPIC_ERROR(xaglogger, ##__VA_ARGS__)
// #define SPDLOG_CRITICAL(...) XAGLOG_TOPIC_CRITICAL(xaglogger, ##__VA_ARGS__)

/* ########## Some macro functions can use ########## */
/* ########## Macro xaglog log ########## */
// Singleton macro
#define XAGLOG_SINGLETON() xag_nav::os::xaglogSingleton::Instance()

// Based on the macro

// Please initialize xaglog before use
#define XAGLOG_TOPIC_INIT(pkg_name, app_name, log_topic)           \
  do {                                                             \
    XAGLOG_SINGLETON()->add_xaglog(pkg_name, app_name, log_topic); \
  } while (0)

#define XAGLOG_INIT(pkg_name, app_name, log_topic)                         \
  do {                                                                     \
    XAGLOG_SINGLETON()->set_default_xaglog(pkg_name, app_name, log_topic); \
  } while (0)

// log output macro
#define XAGLOG_LOGGER(log_topic) XAGLOG_SINGLETON()->get_xaglog(log_topic)

#define XAGLOG_LOGGER_CALL(log_topic, lvl, ...)                            \
  do {                                                                     \
    if (XAGLOG_LOGGER(log_topic) != nullptr) {                             \
      XAGLOG_LOGGER(log_topic)->log(                                       \
          spdlog::source_loc{__FILE__, __LINE__, SPDLOG_FUNCTION}, lvl,    \
          ##__VA_ARGS__);                                                  \
    } else {                                                               \
      SPDLOG_LOGGER_CALL(spdlog::default_logger_raw(),                     \
                         spdlog::level::critical,                          \
                         "log_topic : \'{}\' does not exist!", log_topic); \
    }                                                                      \
  } while (0)

// use fmt lib, e.g. XAGLOG_TRACE("warn log, {1}, {1}, {2}", 1, 2);
#define XAGLOG_TRACE(...)                                     \
  XAGLOG_LOGGER_CALL(XAGLOG_SINGLETON()->default_log_topic(), \
                     spdlog::level::trace, ##__VA_ARGS__)
#define XAGLOG_DEBUG(...)                                     \
  XAGLOG_LOGGER_CALL(XAGLOG_SINGLETON()->default_log_topic(), \
                     spdlog::level::debug, ##__VA_ARGS__)
#define XAGLOG_INFO(...)                                      \
  XAGLOG_LOGGER_CALL(XAGLOG_SINGLETON()->default_log_topic(), \
                     spdlog::level::info, ##__VA_ARGS__)
#define XAGLOG_WARN(...)                                      \
  XAGLOG_LOGGER_CALL(XAGLOG_SINGLETON()->default_log_topic(), \
                     spdlog::level::warn, ##__VA_ARGS__)
#define XAGLOG_ERROR(...)                                     \
  XAGLOG_LOGGER_CALL(XAGLOG_SINGLETON()->default_log_topic(), \
                     spdlog::level::err, ##__VA_ARGS__)
#define XAGLOG_CRITICAL(...)                                  \
  XAGLOG_LOGGER_CALL(XAGLOG_SINGLETON()->default_log_topic(), \
                     spdlog::level::critical, ##__VA_ARGS__)

#define XAGLOG_TOPIC_TRACE(log_topic, ...) \
  XAGLOG_LOGGER_CALL(log_topic, spdlog::level::trace, ##__VA_ARGS__)
#define XAGLOG_TOPIC_DEBUG(log_topic, ...) \
  XAGLOG_LOGGER_CALL(log_topic, spdlog::level::debug, ##__VA_ARGS__)
#define XAGLOG_TOPIC_INFO(log_topic, ...) \
  XAGLOG_LOGGER_CALL(log_topic, spdlog::level::info, ##__VA_ARGS__)
#define XAGLOG_TOPIC_WARN(log_topic, ...) \
  XAGLOG_LOGGER_CALL(log_topic, spdlog::level::warn, ##__VA_ARGS__)
#define XAGLOG_TOPIC_ERROR(log_topic, ...) \
  XAGLOG_LOGGER_CALL(log_topic, spdlog::level::err, ##__VA_ARGS__)
#define XAGLOG_TOPIC_CRITICAL(log_topic, ...) \
  XAGLOG_LOGGER_CALL(log_topic, spdlog::level::critical, ##__VA_ARGS__)

#ifdef XAGLOG_USE_PRINTF

#define XAGLOG_LOGGER_PRINTF_CALL(log_topic, lvl, msg, ...)                \
  do {                                                                     \
    if (XAGLOG_LOGGER(log_topic) != nullptr) {                             \
      XAGLOG_LOGGER(log_topic)->log(                                       \
          spdlog::source_loc{__FILE__, __LINE__, SPDLOG_FUNCTION}, lvl,    \
          fmt::sprintf(msg, ##__VA_ARGS__));                               \
    } else {                                                               \
      SPDLOG_LOGGER_CALL(spdlog::default_logger_raw(),                     \
                         spdlog::level::critical,                          \
                         "log_topic : \'{}\' does not exist!", log_topic); \
    }                                                                      \
  } while (0)

// use like sprintf, e.g. PRINT_WARN("warn log, %d-%d", 1, 2);
#define XAGLOG_TRACE_PRINTF(msg, ...)                                \
  XAGLOG_LOGGER_PRINTF_CALL(XAGLOG_SINGLETON()->default_log_topic(), \
                            spdlog::level::trace, msg, ##__VA_ARGS__)
#define XAGLOG_DEBUG_PRINTF(msg, ...)                                \
  XAGLOG_LOGGER_PRINTF_CALL(XAGLOG_SINGLETON()->default_log_topic(), \
                            spdlog::level::debug, msg, ##__VA_ARGS__)
#define XAGLOG_INFO_PRINTF(msg, ...)                                 \
  XAGLOG_LOGGER_PRINTF_CALL(XAGLOG_SINGLETON()->default_log_topic(), \
                            spdlog::level::info, msg, ##__VA_ARGS__)
#define XAGLOG_WARN_PRINTF(msg, ...)                                 \
  XAGLOG_LOGGER_PRINTF_CALL(XAGLOG_SINGLETON()->default_log_topic(), \
                            spdlog::level::warn, msg, ##__VA_ARGS__)
#define XAGLOG_ERROR_PRINTF(msg, ...)                                \
  XAGLOG_LOGGER_PRINTF_CALL(XAGLOG_SINGLETON()->default_log_topic(), \
                            spdlog::level::err, msg, ##__VA_ARGS__)
#define XAGLOG_CRITICAL_PRINTF(msg, ...)                             \
  XAGLOG_LOGGER_PRINTF_CALL(XAGLOG_SINGLETON()->default_log_topic(), \
                            spdlog::level::critical, msg, ##__VA_ARGS__)

#define XAGLOG_LOGGER_TRACE_PRINTF(log_topic, msg, ...) \
  XAGLOG_LOGGER_PRINTF_CALL(log_topic, spdlog::level::trace, msg, ##__VA_ARGS__)
#define XAGLOG_LOGGER_DEBUG_PRINTF(log_topic, msg, ...) \
  XAGLOG_LOGGER_PRINTF_CALL(log_topic, spdlog::level::debug, msg, ##__VA_ARGS__)
#define XAGLOG_LOGGER_INFO_PRINTF(log_topic, msg, ...) \
  XAGLOG_LOGGER_PRINTF_CALL(log_topic, spdlog::level::info, msg, ##__VA_ARGS__)
#define XAGLOG_LOGGER_WARN_PRINTF(log_topic, msg, ...) \
  XAGLOG_LOGGER_PRINTF_CALL(log_topic, spdlog::level::warn, msg, ##__VA_ARGS__)
#define XAGLOG_LOGGER_ERROR_PRINTF(log_topic, msg, ...) \
  XAGLOG_LOGGER_PRINTF_CALL(log_topic, spdlog::level::err, msg, ##__VA_ARGS__)
#define XAGLOG_LOGGER_CRITICAL_PRINTF(log_topic, msg, ...)           \
  XAGLOG_LOGGER_PRINTF_CALL(log_topic, spdlog::level::critical, msg, \
                            ##__VA_ARGS__)

#endif

#ifdef XAGLOG_USE_STREAM

#define XAGLOG_LOGGER_STREAM_CALL(log_topic, lvl) \
  xag_nav::os::xaglog_stream(                     \
      log_topic, spdlog::source_loc{__FILE__, __LINE__, SPDLOG_FUNCTION}, lvl)

// use like stream , e.g. STM_WARN() << "warn log: " << 1;
#define XAGLOG_TRACE_STREAM()                                        \
  XAGLOG_LOGGER_STREAM_CALL(XAGLOG_SINGLETON()->default_log_topic(), \
                            spdlog::level::trace)
#define XAGLOG_DEBUG_STREAM()                                        \
  XAGLOG_LOGGER_STREAM_CALL(XAGLOG_SINGLETON()->default_log_topic(), \
                            spdlog::level::debug)
#define XAGLOG_INFO_STREAM()                                         \
  XAGLOG_LOGGER_STREAM_CALL(XAGLOG_SINGLETON()->default_log_topic(), \
                            spdlog::level::info)
#define XAGLOG_WARN_STREAM()                                         \
  XAGLOG_LOGGER_STREAM_CALL(XAGLOG_SINGLETON()->default_log_topic(), \
                            spdlog::level::warn)
#define XAGLOG_ERROR_STREAM()                                        \
  XAGLOG_LOGGER_STREAM_CALL(XAGLOG_SINGLETON()->default_log_topic(), \
                            spdlog::level::err)
#define XAGLOG_CRITICAL_STREAM()                                     \
  XAGLOG_LOGGER_STREAM_CALL(XAGLOG_SINGLETON()->default_log_topic(), \
                            spdlog::level::critical)

#define XAGLOG_LOGGER_TRACE_STREAM(log_topic) \
  XAGLOG_LOGGER_STREAM_CALL(log_topic, spdlog::level::trace)
#define XAGLOG_LOGGER_DEBUG_STREAM(log_topic) \
  XAGLOG_LOGGER_STREAM_CALL(log_topic, spdlog::level::debug)
#define XAGLOG_LOGGER_INFO_STREAM(log_topic) \
  XAGLOG_LOGGER_STREAM_CALL(log_topic, spdlog::level::info)
#define XAGLOG_LOGGER_WARN_STREAM(log_topic) \
  XAGLOG_LOGGER_STREAM_CALL(log_topic, spdlog::level::warn)
#define XAGLOG_LOGGER_ERROR_STREAM(log_topic) \
  XAGLOG_LOGGER_STREAM_CALL(log_topic, spdlog::level::err)
#define XAGLOG_LOGGER_CRITICAL_STREAM(log_topic) \
  XAGLOG_LOGGER_STREAM_CALL(log_topic, spdlog::level::critical)

#endif

#include <atomic>
#include <mutex>
#include <sstream>
#include <string>
#include <unordered_map>

// spdlog
#include "spdlog/spdlog.h"
#include "spdlog/async.h"

// spdlog sub-modules
#include "spdlog/fmt/bundled/printf.h"
#include "spdlog/sinks/daily_file_sink.h"
#include "spdlog/sinks/rotating_file_sink.h"
#include "spdlog/sinks/stdout_color_sinks.h"

namespace xag_nav {
namespace os {

static std::string default_pkg_name_;
static std::string default_app_name_;
static std::string default_log_topic_;

// spdlog encapsulation
class xaglog {
 public:
  xaglog(const std::string &pkg_name, const std::string &app_name,
         const std::string &log_topic) {
#ifdef XAGLOG_MORE_CAPACITY
    // Create file rotating logger with 10mb size max and 5 rotated files
    auto max_size = 1024 * 1024 * 10;  // 10M
    auto max_files = 5;
#else
    // Create file rotating logger with 5mb size max and 3 rotated files
    auto max_size = 1024 * 1024 * 5;  // 5M
    auto max_files = 3;
#endif

    std::vector<spdlog::sink_ptr> sink_list;

#ifdef XAGLOG_CONSOLE
    auto console_logger_sink =
        std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
    console_logger_sink->set_level(spdlog::level::trace);
    sink_list.push_back(console_logger_sink);
#endif  // XAGLOG_CONSOLE

#ifdef XAGLOG_USE_TRACE_LOG

    const std::string app_trace_log_path = "/data/log/" + pkg_name + "/" + app_name + ".log";

    xaglogger_ = spdlog::create_async<spdlog::sinks::rotating_file_sink_mt>(log_topic, app_trace_log_path, max_size, max_files);

    // auto app_trace_logger_sink =
    //     std::make_shared<spdlog::sinks::rotating_file_sink_mt>(
    //         app_trace_log_path, max_size, max_files);
    // app_trace_logger_sink->set_level(spdlog::level::trace);
    // sink_list.push_back(app_trace_logger_sink);

    // // add default_logger
    // if (!default_log_topic_.empty() && log_topic != default_log_topic_) {
    //   const std::string default_log_log_path =
    //       "/data/log/" + default_pkg_name_ + "/" + default_app_name_ + ".log";
    //   auto default_log_logger_sink =
    //       std::make_shared<spdlog::sinks::rotating_file_sink_mt>(
    //           default_log_log_path, max_size, max_files);
    //   default_log_logger_sink->set_level(spdlog::level::trace);
    //   sink_list.push_back(default_log_logger_sink);
    // }

#else

    // const std::string app_tmp_log_path =
    //     "/tmp/" + pkg_name + "/" + app_name + ".log";
    // auto app_tmp_logger_sink =
    //     std::make_shared<spdlog::sinks::rotating_file_sink_mt>(
    //         app_tmp_log_path, max_size, max_files);
    // app_tmp_logger_sink->set_level(spdlog::level::trace);
    // sink_list.push_back(app_tmp_logger_sink);

    // if (!default_log_topic_.empty() && log_topic != default_log_topic_) {
    //   const std::string default_log_log_path =
    //       "/tmp/" + default_pkg_name_ + "/" + default_app_name_ + ".log";
    //   auto default_log_logger_sink =
    //       std::make_shared<spdlog::sinks::rotating_file_sink_mt>(
    //           default_log_log_path, max_size, max_files);
    //   default_log_logger_sink->set_level(spdlog::level::trace);
    //   sink_list.push_back(default_log_logger_sink);
    // }

#endif

    // xaglogger_ = std::make_shared<spdlog::logger>(log_topic, sink_list.begin(),
    //                                               sink_list.end());

    xaglogger_->set_level(spdlog::level::trace);
    //xaglogger_->set_pattern("[%Y-%m-%d %H:%M:%S.%e][%l]  -- %v"); //设置默认spdloglog的格式
    // If it is info level log is written immediately
    xaglogger_->flush_on(spdlog::level::info);
  }

  ~xaglog() {}

  std::shared_ptr<spdlog::logger> get_xaglogger() { return xaglogger_; }

 private:
  xaglog(const xaglog &) = delete;
  xaglog &operator=(const xaglog &) = delete;

  std::shared_ptr<spdlog::logger> xaglogger_;
};

class xaglogSingleton {
 public:
  static xaglogSingleton *Instance(bool create_if_needed = true) {
    static xaglogSingleton *pInstance = nullptr;
    static std::mutex mutex;
    if (!pInstance && create_if_needed) {
      std::lock_guard<std::mutex> lock(mutex);
      if (!pInstance) {
        pInstance = new xaglogSingleton();
      }
    }
    return pInstance;
  }

  bool add_xaglog(const std::string &pkg_name, const std::string &app_name,
                  const std::string &log_topic) {
    if (!xaglog_map_.empty()) {
      if (xaglog_map_.find(log_topic) != xaglog_map_.end()) {
        return false;
      }
    }

    xaglog_map_.insert(std::pair<std::string, xaglog *>(
        log_topic, new class xaglog(pkg_name, app_name, log_topic)));

    return true;
  }

  bool delete_xaglog(const std::string &log_topic) {
    if (xaglog_map_.empty() ||
        xaglog_map_.find(log_topic) == xaglog_map_.end()) {
      return false;
    }

    delete xaglog_map_.at(log_topic);
    xaglog_map_.erase(log_topic);

    return true;
  }

  std::shared_ptr<spdlog::logger> get_xaglog(const std::string &log_topic) {
    if (xaglog_map_.empty()) {
      return nullptr;
    }

    if (xaglog_map_.find(log_topic) != xaglog_map_.end()) {
      return xaglog_map_.at(log_topic)->get_xaglogger();
    }

    return nullptr;
  }

  bool set_default_xaglog(const std::string &pkg_name,
                          const std::string &app_name,
                          const std::string &log_topic) {
    if (this->add_xaglog(pkg_name, app_name, log_topic)) {
      default_pkg_name_ = pkg_name;
      default_app_name_ = app_name;
      default_log_topic_ = log_topic;
      spdlog::set_default_logger(this->get_xaglog(log_topic));
      return true;
    }
    return false;
  }

  const std::string &default_log_topic() { return default_log_topic_; }

 private:
  xaglogSingleton() {}
  ~xaglogSingleton() {}
  xaglogSingleton(const xaglogSingleton &) = delete;
  xaglogSingleton &operator=(const xaglogSingleton &) = delete;

  // key : log_topic
  std::unordered_map<std::string, xaglog *> xaglog_map_;
};

#ifdef XAGLOG_USE_STREAM

#include <iostream>
#include <sstream>

// let logger like stream
class xaglog_stream : public std::ostringstream {
 public:
  xaglog_stream(const std::string &log_topic, const spdlog::source_loc &source,
                const spdlog::level::level_enum &lvl)
      : log_topic_(log_topic), source_(source), lvl_(lvl) {}

  ~xaglog_stream() {
    if (XAGLOG_LOGGER(log_topic_) != nullptr) {
      XAGLOG_LOGGER(log_topic_)->log(source_, lvl_, str());
    } else {
      SPDLOG_LOGGER_CALL(spdlog::default_logger_raw(), spdlog::level::critical,
                         "log_topic : \'{}\' does not exist!", log_topic_);
    }
  }

 private:
  std::string log_topic_;
  spdlog::source_loc source_;
  spdlog::level::level_enum lvl_;
};

#endif

}  // namespace os
}  // namespace xag_nav

#endif
